/*
 * @Author: 张宇飞
 * @Date: 2020-02-25 19:24:15
 * @LastEditors: 张宇飞
 * @LastEditTime: 2020-02-26 22:36:20
 * @Description: 条件变量本身不是锁，它需要配合互斥锁来使用，减少不必要的竞争
 * 互斥锁必须是普通锁（PTHREAD_MUTEX_TIMED_NP）或者适应锁（PTHREAD_MUTEX_ADAPTIVE_NP）
 * @Email: zhangyufei49@163.com
 */
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "utils.h"

#define print_test_header() \
    fprintf(stderr, "\n------------ %s ------------\n\n", __func__)

static pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;
static int gnum = 0;
static int alive = 1;

static void signal_handle(int sig) {
    fprintf(stderr, "recive signal %d starting quitting\n", sig);
    alive = 0;
}

static void* producer(void* arg) {
    printf("producer started\n");
    while (alive) {
        pthread_mutex_lock(&glock);
        gnum++;
        printf("in producer gnum = %d\n", gnum);
        pthread_mutex_unlock(&glock);
        // 激活等待队列中的第一个
        pthread_cond_signal(&gcond);
        sleep(1);
    }
    printf("producer stopping\n");
    return NULL;
}

static void* consumer(void* arg) {
    printf("consumer started\n");
    while (1) {
        pthread_mutex_lock(&glock);
        if (!alive && gnum < 1) {
            break;
        }

        if (alive && gnum == 0) {
            // 这个函数做三件事情:
            // 1.
            // 解锁互斥量（这就是说，前面必须在本线程内加锁。不然后面的状态将不可预期）
            // 2. 进入阻塞，当前线程休眠。等待被唤醒
            // 3. 恢复线程。等待 pthread_mutex_lock 上锁成功后返回
            pthread_cond_wait(&gcond, &glock);
        }
        gnum--;
        printf("in consumer gnum = %d\n", gnum);
        pthread_mutex_unlock(&glock);
        sleep(1);
    }

    printf("consumer stopping\n");
    return NULL;
}

static void* listener(void* arg) {
    pthread_mutex_lock(&glock);
    fprintf(stderr, "thread %ul will wait cond\n",
            (unsigned long)pthread_self());
    pthread_cond_wait(&gcond, &glock);
    fprintf(stderr, "thread %ul is woken up\n", (unsigned long)pthread_self());
    pthread_mutex_unlock(&glock);
    return NULL;
}

static void* impatient_listener(void* arg) {
    pthread_mutex_lock(&glock);
    fprintf(stderr, "thread %ul will wait cond 1 sec\n",
            (unsigned long)pthread_self());
    time_t cur = time(NULL);
    struct timespec t;
    t.tv_sec = cur + 1;
    pthread_cond_timedwait(&gcond, &glock, &t);
    pthread_mutex_unlock(&glock);
    fprintf(stderr, "thread %ul is gone\n", (unsigned long)pthread_self());
    return NULL;
}

static void* broadcaster(void* arg) {
    // 等待其他线程启动
    sleep(2);
    fprintf(stderr, "thread %ul broadcast wakeup signal\n",
            (unsigned long)pthread_self());
    pthread_cond_broadcast(&gcond);
    return NULL;
}

static void test_broadcast() {
    print_test_header();
    run_threads(listener, listener, impatient_listener, broadcaster, NULL);
}

static void test_signal() {
    print_test_header();
    signal(SIGTERM, signal_handle);
    signal(SIGINT, signal_handle);
    run_threads(producer, consumer, NULL);
}

int main(int argc, char const* argv[]) {
    test_broadcast();
    test_signal();
    return 0;
}
